#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import socket
import threading
from string import split

################  PARAMETERS  ################
ban_time = 1 # A banned user can't connect for x hours.
max_bytes = 8192 # Maximum size of a message (bytes).
##############################################

active_threads = {}

chat_help = ['nickname#>"message" : private message',
             'nickname$>"message" : public message',
             '*>nickname "message" : message about a user',
             '/help : show this help.',
             '/quit : disconnect yourself.',
             '/nick or /nick nickname : change your nickname',
             '/me "msg" : send a message about you.',
             '/user nickname "msg" : send a private message.',
             '/list : print users list']

admin_help = ['/kick nickname : disconnect a user',
              '/ban nickname : ban a user']

admin_accounts = {'max-k': 'fuckyou22',
                  'H_leOuf': 'freedom22'}

class ClientThread(threading.Thread):

    def __init__(self, connection, address):
        self.connection = connection
        self.address = address
        threading.Thread.__init__(self)

    def log(self, log_msg, remote_thread=False):
        if remote_thread != False:
            print(remote_thread[0][1], "-", remote_thread[1][1], ":", log_msg)
        else:
            print(self.ident, "-", self.address[0], ":", log_msg)

    def send(self, send_msg, flag=1, connection=False):
        if flag == 1:
            send_msg = send_msg + '\r\n'
        elif flag == 2:
            send_msg = send_msg + '\r\n\r\n'
        if connection != False:
            connection.send(send_msg)
        else:
            self.connection.send(send_msg)

    def hello(self):
        self.log('Remote host connected.')
        self.send('')
        self.send('Your are connected to a very simple full-python IPv4 chat system.')
        self.set_nick()
        self.send('Hello, ' + self.nickname + '. Enter /help to see the help.')
        self.send('')

    def check_inuse(self, nickname):
        if self.nickname != ' ':
            for key in active_threads.iterkeys():
                if key[0] == self.nickname:
                    return True

    def receive(self, bytes=max_bytes):
        received = self.connection.recv(bytes).splitlines()[0]
        if received != '':
            return received
        else:
            return ' '

    def check_too_long(self, nick_entry):
        if len(nick_entry) > 32:
            return nick_entry[:32]
        else:
            return nick_entry

    def check_password(self, nick):
        self.send('')
        self.send(nick + ' is an admin account.')
        self.send('please enter your password or press Enter to cancel :', 0)
        password = self.receive()
        if password == ' ' or password != admin_accounts.get(nick):
            return False
        else:
            return True

    def set_nick(self, change=False, nick=False):
        if change != False:
            del active_threads[(self.nickname, self.ident)]
        self.nickname = ' '
        while self.nickname == ' ' or self.check_inuse(self.nickname):
            if nick == False:
                self.send('')
                self.send('Please enter your nickname : ', 0)
                self.nickname = self.receive()
            else:
                self.nickname = nick
            if self.nickname == ' ':
                self.send('')
                self.send('Nothing is not a nickname. Please enter your nickname : ', 0)
            elif self.check_inuse(self.nickname) == True:
                self.send('')
                self.send('Nickname already in use, please choose another one : ', 0)
            elif self.nickname in admin_accounts:
                if self.check_password(self.nickname) == False:
                    self.send('Error : Wrong password.')
                    self.nickname = ' '
            nick = False
        if self.nickname != self.check_too_long(self.nickname):
            self.nickname = self.check_too_long(self.nickname)
            self.send('Your entry was too long.')
            self.send('Your nickname has been truncated to thirty-two characters.')
        self.send('Your nickname is now ' + self.nickname + '.')
        active_threads[(self.nickname, self.ident)] = (self.connection, self.address)
        self.send('')

    def quit(self, remote_thread=False):
        if remote_thread != False:
            self.log('Remote host disconnected.', remote_thread)
            del active_threads[remote_thread[0]]
            remote_thread[1][0].close()
        else:
            self.log('Remote host disconnected.')
            del active_threads[(self.nickname, self.ident)]
            self.connection.close()

    def help(self):
        self.send('')
        self.send('Take a look at the help :', 2)
        for item in chat_help:
            self.send(item)
        if self.nickname in admin_accounts:
            for item in admin_help:
                self.send(item)
        self.send('')

    def check_socket(self, user):
        for key in active_threads.iterkeys():
            if key[0] == user:
                socket = active_threads.get(key)
                return socket[0]

    def list(self):
        users_list = []
        self.send('')
        for key in active_threads.iterkeys():
            users_list.append(key[0])
        if len(users_list) == 1:
            self.send('sorry ' + users_list[0] + ', you are alone in the dark.')
        else:
            users_line = []
            self.send('Connected users :')
            for user in users_list:
                users_line.append(user)
                if len(", ".join(users_line)) > 78:
                    self.send(", ".join(users_line[:-1]))
                    del users_line[:-1]
            self.send(", ".join(users_line))
        self.send('')

    def commande(self, commande):
        if commande == 'help':
            self.help()
        elif commande == 'quit':
            self.quit()
        elif commande == 'nick':
            if len(split(self.message)) > 1:
                self.set_nick(True, split(self.message)[1])
            else:
                self.set_nick(True)
        elif commande == 'user':
            msg_recipient = split(self.message)[1]
            msg_connection = self.check_socket(msg_recipient)
            msg_content = self.nickname + '#>' + " ".join(split(self.message)[2:])
            self.send(msg_content, 1, msg_connection)
        elif commande == 'me':
            msg_content = '*>' + self.nickname + ' ' + " ".join(split(self.message[1:]))
            self.send(msg_content)
        elif commande == 'list':
            self.list()
        elif commande == 'kick':
            if self.nickname not in admin_accounts:
                self.send('Error : You must be an administrator to do this.')
                self.send('')
            else:
                thread = ' '
                for key in active_threads.iterkeys():
                    if key[0] == split(self.message)[1]:
                        thread = (key, active_threads.get(key))
                        break
                if thread != ' ':
                    self.quit(thread)
        else:
            self.send('')
            self.send(commande + ' : command not found.', 2)
            self.send('')

    def run(self):
        self.hello()
        while 1:
            self.message = self.receive()
            if self.message != ' ':
                if self.message[0] == '/':
                    self.commande(split(self.message)[0][1:])
                else:
                    for key in active_threads.iterkeys():
                        if key[0] != self.nickname:
                            self.send(self.nickname + '$>' + self.message, 1, active_threads.get(key)[0])

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 9998))
server.listen(100)

while True:
    connection, address = server.accept()
    ClientThread(connection, address).start()